package top.mcwebsite.playandroid.lib.mvi.base

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
import kotlin.reflect.KProperty1

abstract class BaseViewModel<I: IIntent, U: IUiState, E: IUiEffect>(
    defaultState: U,
) : ViewModel() {

    private val intentChannel = Channel<I>(Channel.UNLIMITED)

    private val _uiStateFlow = MutableStateFlow(defaultState)
    private val _uiEffectFlow = MutableSharedFlow<E>()

    val uiStateFlow = _uiStateFlow.asStateFlow()
    val uiEffectFlow: Flow<E> = _uiEffectFlow.asSharedFlow()

    val uiState: U get() {
        return _uiStateFlow.value
    }

    init {
        viewModelScope.launch {
            intentChannel.receiveAsFlow().collect {
                handleIntent(it)
            }
        }
    }

    protected fun setUiState(reducer: U.() -> U) {
        val newUIStatus = uiState.reducer()
        if (newUIStatus != uiState) {
            _uiStateFlow.value = newUIStatus
        }
    }

    protected fun sendUiEffect(uiEffect: E) {
        viewModelScope.launch {
            _uiEffectFlow.emit(uiEffect)
        }
    }

    fun sendIntent(intent: I) {
        viewModelScope.launch {
            intentChannel.send(intent)
        }
    }

    protected abstract fun handleIntent(intent: I)

    @Composable
    fun HandleUiEffect(action: suspend (E) -> Unit) {
        val lifecycleOwner = LocalLifecycleOwner.current
        LaunchedEffect(uiEffectFlow, lifecycleOwner) {
            lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                uiEffectFlow.collect {
                    action.invoke(it)
                }
            }
        }
    }


    internal fun <T: Any> Flow<T>.subscription(
        lifecycleOwner: LifecycleOwner?,
        block: suspend (T) -> Unit,
    ): Job {
        return if (lifecycleOwner != null) {
            collectLatest(lifecycleOwner, block)
        } else {
            viewModelScope.launch {
                collectLatest(block)
            }
        }
    }

    protected fun <A> onEach(
        prop: KProperty1<U, A>,
        block: suspend (A) -> Unit,
    ) {
        internalSubscribe(null, prop, block)
    }

    protected fun <A, B> onEach(
        prop1: KProperty1<U, A>,
        prop2: KProperty1<U, B>,
        block: suspend (A, B) -> Unit,
    ) {
        internalSubscribe(null, prop1, prop2, block)
    }

}